#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Profiler.h"
#include "BasicBlock.h"
#include "CFG.h"
#include "../6502_CPUMacros.h"
#include "../6502.h"
#include "../NESMemory.h"
#include "../Statistics.h"
#include "../JRYNES.h"

extern Statistics* ExecutionStatistics;
extern EmulationSettings Settings;

static const char* adc(){const char *Text = "ADC"; return Text;}
static const char* and(){const char *Text = "AND"; return Text;}
static const char* asl(){const char *Text = "ASL"; return Text;}
static const char* bcc(){const char *Text = "BCC"; return Text;}
static const char* bcs(){const char *Text = "BCS"; return Text;}
static const char* beq(){const char *Text = "BEQ"; return Text;}
static const char* bit(){const char *Text = "BIT"; return Text;}
static const char* bmi(){const char *Text = "BMI"; return Text;}
static const char* bne(){const char *Text = "BNE"; return Text;}
static const char* bpl(){const char *Text = "BPL"; return Text;}
static const char* brk(){const char *Text = "BRK"; return Text;}
static const char* bvc(){const char *Text = "BVC"; return Text;}
static const char* bvs(){const char *Text = "BVS"; return Text;}
static const char* clc(){const char *Text = "CLC"; return Text;}
static const char* cld(){const char *Text = "CLD"; return Text;}
static const char* cli(){const char *Text = "CLI"; return Text;}
static const char* clv(){const char *Text = "CLV"; return Text;}
static const char* cmp(){const char *Text = "CMP"; return Text;}
static const char* cpx(){const char *Text = "CPX"; return Text;}
static const char* cpy(){const char *Text = "CPY"; return Text;}
static const char* dec(){const char *Text = "DEC"; return Text;}
static const char* dex(){const char *Text = "DEX"; return Text;}
static const char* dey(){const char *Text = "DEY"; return Text;}
static const char* eor(){const char *Text = "EOR"; return Text;}
static const char* inc(){const char *Text = "INC"; return Text;}
static const char* inx(){const char *Text = "INX"; return Text;}
static const char* iny(){const char *Text = "INY"; return Text;}
static const char* jmp(){const char *Text = "JMP"; return Text;}
static const char* jsr(){const char *Text = "JSR"; return Text;}
static const char* lda(){const char *Text = "LDA"; return Text;}
static const char* ldx(){const char *Text = "LDX"; return Text;}
static const char* ldy(){const char *Text = "LDY"; return Text;}
static const char* lsr(){const char *Text = "LSR"; return Text;}
static const char* nop(){const char *Text = "NOP"; return Text;}
static const char* ora(){const char *Text = "ORA"; return Text;}
static const char* pha(){const char *Text = "PHA"; return Text;}
static const char* php(){const char *Text = "PHP"; return Text;}
static const char* pla(){const char *Text = "PLA"; return Text;}
static const char* plp(){const char *Text = "PLP"; return Text;}
static const char* rol(){const char *Text = "ROL"; return Text;}
static const char* ror(){const char *Text = "ROR"; return Text;}
static const char* rti(){const char *Text = "RTI"; return Text;}
static const char* rts(){const char *Text = "RTS"; return Text;}
static const char* sbc(){const char *Text = "SBC"; return Text;}
static const char* sec(){const char *Text = "SEC"; return Text;}
static const char* sed(){const char *Text = "SED"; return Text;}
static const char* sei(){const char *Text = "SEI"; return Text;}
static const char* sta(){const char *Text = "STA"; return Text;}
static const char* stx(){const char *Text = "STX"; return Text;}
static const char* sty(){const char *Text = "STY"; return Text;}
static const char* tax(){const char *Text = "TAX"; return Text;}
static const char* tay(){const char *Text = "TAY"; return Text;}
static const char* tsx(){const char *Text = "TSX"; return Text;}
static const char* txa(){const char *Text = "TXA"; return Text;}
static const char* txs(){const char *Text = "TXS"; return Text;}
static const char* tya(){const char *Text = "TYA"; return Text;}
static const char* aso(){const char *Text = "ASO"; return Text;}
static const char* dop(){const char *Text = "DOP"; return Text;}
static const char* top(){const char *Text = "TOP"; return Text;}
static const char* jam(){const char *Text = "JAM"; return Text;}       

const InstructionProfilingData ProfilingData[256] = {
    #include "../6502_OpcodeMap.h"
};

ProfData* ProfilerData;
extern NESMainMemory NESRAM;

void NewEpoch(void){
    ProfilerData->Epoch++;
    ProfilerData->EpochCounter = Settings.EpochLength;
    
    if(ExecutionStatistics->CurrentEpochInfo)    
        LST_Insert(ExecutionStatistics->EpochInformation, ExecutionStatistics->CurrentEpochInfo);
        
    ExecutionStatistics->CurrentEpochInfo = EpochInfoNew(ProfilerData->Epoch);
    
    if(ProfilerData->CurrentCFG){    
        CFG_Reset(ProfilerData->CurrentCFG);
    }
}

void ProfilerInit(void){
    ProfilerData = malloc(sizeof(ProfData));
    
    ProfilerData->CurrentBasicBlock = NULL;
    ProfilerData->CurrentTU = NULL;
    ProfilerData->CFGs = LST_New(NULL);
    ProfilerData->Epoch = 0;
    ProfilerData->EpochCounter = Settings.EpochLength;
    ProfilerData->LastCF = 0;
    ProfilerData->CurrentCFG = NULL;
    ProfilerData->Interpreting = 1;
    
    NewEpoch();
}

static BBInstruction* GetInstructionData(uint16_t Address){
    BBInstruction* New;
    uint32_t NumOperands;
    uint32_t i = 0;
    
    New = BBINew(Address);
    
    New->Opcode = READ_MEMORY(Address);
    
    NumOperands = CPUGetInstructionSize(New->Opcode);
    
    New->Size = NumOperands;
    
    NumOperands--;
    
    for(i = 0; i < NumOperands; i++)
        New->Operands[i] = READ_MEMORY(Address + i + 1);
    
    New->Operands[i] = 0;
    
    New->AddressingMode = ProfilingData[New->Opcode].AddressingMode;
    
    New->ControlFlow = ProfilingData[New->Opcode].CTInstruction;
    
    return New;
}

void SetNewCFG(void){
    ProfilerData->CurrentCFG = CFG_New();
    LST_Insert(ProfilerData->CFGs, ProfilerData->CurrentCFG);
}

void ProfileIntRoutine(uint16_t Address){
    BasicBlock *BB, *SplitBB;
    BBInstruction *BBI;
    TranslationUnit* PreviousTU = NULL;
    TranslationUnit* SplitTU = NULL;
    
#define CLOSE_BB \
        BBRRefresh(TU_GetBB(ProfilerData->CurrentBasicBlock)); \
        /*CFG_AddTUNode(ProfilerData->CurrentCFG, ProfilerData->CurrentBasicBlock);*/ \
        ProfilerData->CurrentBasicBlock = NULL;
    
    Address--;
    
    //Get the basic block for the current address
    BB = BBRSearch(Address);
    
    //If the current instruction is not inside an existing basic block
    if(!BB){
        //If no basic block, create one
        if(!ProfilerData->CurrentBasicBlock){
            
            #ifdef PROFILER_DEBUG
            ExecutionStatistics->DBT_TotalBBs++;
            printf("New Basic Block: 0x%04X\n", Address);
            #endif
            
            //Create a translation unit for the new basic block
            ProfilerData->CurrentBasicBlock = TU_New();
            TU_SetBB(ProfilerData->CurrentBasicBlock, BBNew(Address));
            
            ProfilerData->CurrentTU = ProfilerData->CurrentBasicBlock;
            
            CFG_AddBBNode(ProfilerData->CurrentCFG, ProfilerData->CurrentBasicBlock);
        }
        
        //NOTE: GetInstructionData is currently not Mapper-safe.
        BBI = GetInstructionData(Address);
        
        //Insert Current instruction into the current basic block
        BBInsertInstruction(TU_GetBB(ProfilerData->CurrentBasicBlock), BBI);

        BB = TU_GetBB(ProfilerData->CurrentBasicBlock);        
        
        //This may be inneficient.
        CFGRAddressSet(ProfilerData->CurrentCFG, Address);
        
        //If the current instruction is a control-flow instruction,
        //terminate the current basic block
        if(BBI->ControlFlow){
            CLOSE_BB
        }
        
    }
    else{
        // The basic block is also terminated when another basic block is found
        if(ProfilerData->CurrentBasicBlock){
            CLOSE_BB
        }
        
        //It's necessary to check whether or not this is a new target. If so,
        //The original Basic Block must be split.
        if(ProfilerData->LastCF){
            if(Address != BB->StartingAddress){
                //TODO: Signal the profiler that a new target was found and that's
                //necessary to retranslate everything that had the previous
                //version of this basic block. If not treated, this can lead
                //to broken jump tables later on.
                
                #ifdef PROFILER_DEBUG
                ExecutionStatistics->DBT_TotalBBs++;
                ExecutionStatistics->DBT_TotalBBSplits++;
                printf("Basic Block Split: 0x%04X(%d) at 0x%04X\n", BB->StartingAddress, BB->NumInstructions, Address);
                #endif                   
                
                SplitBB = BBSplit(BB, Address);
                BBRRefresh(SplitBB);             
                
                //Create a new Translation Unit for the resulting BB
                //This is trivial since we know that the BB was split into two at a certain address
                SplitTU = TU_New();
                TU_SetBB(SplitTU, SplitBB);
                CFG_AddBBNode(ProfilerData->CurrentCFG, SplitTU);
                 
                #ifdef TRANSITION_DEBUG
                printf("Split Transition Detected from 0x%04X to 0x%04X\n", TU_GetStartingAddress(CFG_SearchNode(CFGRSearch(BB->StartingAddress), BB->StartingAddress)->Unit), TU_GetStartingAddress(SplitTU));
                #endif                

                BB = SplitBB;
                
                //If a segfault is ever raised, this is a serious logical error.
            }
        }
        BBI = BBLookupInstruction(BB, Address);
        
        ProfilerData->CurrentTU = CFG_SearchNode(CFGRSearch(BB->StartingAddress), BB->StartingAddress)->Unit;
    }
    
    if(BBI->ControlFlow)
        ProfilerData->LastCF = 1;
    else
        ProfilerData->LastCF = 0;
    
    if(BB->StartingAddress == Address)
        CFG_SearchNode(CFGRSearch(Address), Address)->EpochExecutionCount++;
    
}

void PROF_PrintCFGs(char* DotFilename){
    ControlFlowGraph* CFG;
    char* Scrap;
    uint32_t i = 0;
    
    Scrap = malloc(strlen(DotFilename) + 10);
    
    LST_Rewind(ProfilerData->CFGs);
    
    while((CFG = LST_Next(ProfilerData->CFGs))){
        sprintf(Scrap, "%s.%d.dot", DotFilename, i);
        i++;
        CFG_PrintCFG(CFG, Scrap);
    }
    
    free (Scrap);
}

void ProfileTranslatedRoutine(uint16_t Address){
    
}
